package cn.haocxy.jfm.func;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;

import cn.haocxy.jfm.file.Clipboard;
import cn.haocxy.jfm.file.FilePath;
import cn.haocxy.jfm.util.Executor;
import cn.haocxy.jfm.util.Test;

public final class FileFunc {

	private static enum PasteMode {
		COPY, MOVE
	}

	/**
	 * 执行情况报告回调
	 * 
	 * @author 蔡心宇
	 *
	 */
	public static interface Callback {

		/**
		 * <p>
		 * 报告执行状态
		 * <p>
		 * succeed为true则执行成功, e设置为null
		 * <p>
		 * succeed为false则执行失败,e为原因
		 */
		void apply(boolean succeed, Exception e);

	}

	private static interface ExRunnable {

		void run() throws Exception;

	}

	private FileFunc() {
	}

	public static void remove(Collection<FilePath> files, Callback call) {
		work(call, () -> _remove(files));
	}

	public static void remove(FilePath file, Callback call) {
		work(call, () -> _remove(file));
	}

	/**
	 * 粘贴操作 , 把剪切板中的内容粘贴到dir所指的目录中
	 * <p>
	 * 具体是复制还是移动由剪切板状态决定
	 * 
	 */
	public static void paste(FilePath dir, Callback call) {
		work(call, () -> _paste(dir));
	}

	/**
	 * 把path代表的文件重命名为newName指向的新名字
	 * 
	 * @param path
	 * @param newName
	 */
	public static void rename(FilePath path, String newName, Callback call) {
		work(call, () -> _rename(path, newName));
	}

	private static void work(Callback call, ExRunnable job) {
		Executor.exe(() -> {
			try {
				job.run();
				succeed(call);
			} catch (Exception ex) {
				fail(call, ex);
			}
		});
	}

	private static void succeed(Callback call) {
		if (call != null) {
			call.apply(true, null);
		}
	}

	private static void fail(Callback call, Exception e) {
		if (call != null) {
			Test.ttrace(e);
			call.apply(false, e);
		}
	}

	private static void _remove(Collection<FilePath> paths) throws Exception {
		for (FilePath p : paths) {
			_remove(p);
		}
	}

	private static void _remove(FilePath path) throws Exception {
		File f = path.toPath().toFile();
		if (f.isDirectory()) {
			_clearDir(f);
		}
		f.delete();
	}

	private static void _clearDir(File dir) throws IOException {
		for (File f : dir.listFiles()) {
			if (f.isDirectory()) {
				_clearDir(f);
			}
			f.delete();
		}
	}

	private static void _rename(FilePath path, String newName) throws Exception {
		if (path.getFileName().equals(newName)) {
			return;
		}
		try {
			path.rename(newName);
		} catch (Exception ex) {
			if (ex.getCause() instanceof FileAlreadyExistsException) {
				throw new ExistsException(getExistsMsg(path.getParent()
						.toPath(), newName));
			}
		}
	}

	private static void _paste(FilePath dir) throws Exception {
		Clipboard cb = Clipboard.get();
		List<FilePath> paths = cb.retriveAll();
		Clipboard.Mode cbm = cb.getMode();
		PasteMode mode;
		if (cbm.equals(Clipboard.Mode.COPY)) {
			mode = PasteMode.COPY;
		} else {
			mode = PasteMode.MOVE;
		}
		_paste(mode, paths, dir);
	}

	/**
	 * 把paths中包含的文件粘贴到dir中
	 * <p>
	 * 根据剪切板模式判断是复制还是移动
	 * 
	 * @param paths
	 * @param dir
	 */
	private static void _paste(PasteMode mode, Collection<FilePath> paths,
			FilePath dir) throws Exception {
		if (mode.equals(PasteMode.COPY)) {
			_copyItems(paths, dir);
		} else if (mode.equals(PasteMode.MOVE)) {
			_moveItems(paths, dir);
		}
	}

	private static void _moveItems(Collection<FilePath> paths, FilePath dir)
			throws Exception {
		if (_allInDir(paths, dir)) {
			return;
		}
		for (FilePath src : paths) {
			try {
				_moveItem(src.toPath().getParent(), dir.toPath(),
						src.getFileName());
			} catch (Exception e) {
				// do nothing.
			}
		}
	}

	private static void _copyItems(Collection<FilePath> paths, FilePath dir)
			throws Exception {
		if (_allInDir(paths, dir)) {
			return;
		}
		for (FilePath src : paths) {
			try {
				_copyItem(src.toPath().getParent(), dir.toPath(),
						src.getFileName());
			} catch (Exception e) {
				// do nothing.
			}
		}
	}

	private static void _moveItem(Path srcDir, Path desDir, String name)
			throws Exception {
		Path source = srcDir.resolve(name);
		Path target = desDir.resolve(name);

		Files.walkFileTree(source, EnumSet.of(FileVisitOption.FOLLOW_LINKS),
				Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
					@Override
					public FileVisitResult preVisitDirectory(Path dir,
							BasicFileAttributes attrs) throws IOException {
						Path targetdir = target.resolve(source.relativize(dir));
						try {
							Files.move(dir, targetdir);
						} catch (FileAlreadyExistsException e) {
							if (!Files.isDirectory(targetdir))
								throw e;
						}
						return FileVisitResult.CONTINUE;
					}

					@Override
					public FileVisitResult visitFile(Path file,
							BasicFileAttributes attrs) throws IOException {
						Files.move(file,
								target.resolve(source.relativize(file)));
						return FileVisitResult.CONTINUE;
					}
				});
	}

	private static void _copyItem(Path srcDir, Path desDir, String name)
			throws Exception {
		Path source = srcDir.resolve(name);
		Path target = desDir.resolve(name);

		Files.walkFileTree(source, EnumSet.of(FileVisitOption.FOLLOW_LINKS),
				Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
					@Override
					public FileVisitResult preVisitDirectory(Path dir,
							BasicFileAttributes attrs) throws IOException {
						Path targetdir = target.resolve(source.relativize(dir));
						try {
							Files.copy(dir, targetdir);
						} catch (FileAlreadyExistsException e) {
							if (!Files.isDirectory(targetdir))
								throw e;
						}
						return FileVisitResult.CONTINUE;
					}

					@Override
					public FileVisitResult visitFile(Path file,
							BasicFileAttributes attrs) throws IOException {
						Files.copy(file,
								target.resolve(source.relativize(file)));
						return FileVisitResult.CONTINUE;
					}
				});
	}

	@SuppressWarnings("unused")
	private static void checkExists(Path desDir, String name) {
		if (Files.exists(desDir.resolve(name))) {
			throw new ExistsException(getExistsMsg(desDir, name));
		}
	}

	private static String getExistsMsg(Path dir, String name) {
		return "'" + name + "' 已存在于目录  '" + dir + "'.";
	}

	/**
	 * 是否paths中所有文件项都在dir所指的目录中
	 */
	private static boolean _allInDir(Collection<FilePath> paths, FilePath dir) {
		for (FilePath path : paths) {
			if (!path.getParent().equals(dir)) {
				return false;
			}
		}
		return true;
	}
}
